package auditoryUIbk;

import java.util.Enumeration;
import java.util.Vector;

import jass.engine.*;

/**
   A force model based on looping through a buffer once, loaded from an audio
   file or provided by caller.
   @author Kees van den Doel (kvdoel@cs.ubc.ca)
   Pierre Dragicevic
*/
public class SoundSet extends Out {
	
	  class PendingPlay {
		  StereoSample sample;
		  Object condition;
		  PendingPlay(StereoSample s, Object c) {
			  sample = s;
			  condition = c;
		  }
		  boolean isOK() {
			if (condition == null) // when empty
				return (nplaying == 0);
			if (condition instanceof Integer) // after a given sample
				return !isPlaying(((Integer)condition).intValue());
			return false;
		  }
	  }
	
	  protected String name;
	  protected StereoSample[] samples;
	  protected float volume = 1;
	  protected int channels = 1;
	  protected StereoSample[] playing;
	  protected int nplaying = 0;
	  protected int maxindex = -1;
	  
	  protected float immediateFadeOutRatio = 1; //  no immediate fade out
	  protected float fadeOutRatio = 1; //  no delayed fade out
	  protected float fadeOutTime = 0; //  instantaneous
	  protected Vector pendingPlays = new Vector();
	  
    /** 
     * Creates a new soundset
     */
    public SoundSet(String name, int maxSamples) {
        super(SoundManager.bufferSize); // this is the internal buffer size
        this.name = name;
        playing = new StereoSample[channels];
        samples = new StereoSample[maxSamples];
        
        try {
          SoundManager.player.addSource(this);
        } catch (Exception e) {
          e.printStackTrace();
        }
    }
    
    /**
     * Sets the overall volume of this soundset (0 to 1)
     */
    public void setVolume(float val) {
        volume = val;
    }

    /** 
     * Describes how the samples must be played together in this SoundSet.
     * 
     * default values are:
     *   channels = 10
     *   volumeDecrease = 1
     *   volumeFadeOut = 1
     *   fadeOutTime = 0
     * 
     * @param channels
     * The number of samples that can be played simultaneously.
     * If this number is 1, every new sample played will stop the previous
     * one.
     * 
     * @param volumeDecrease
     * The decrease in the volume of all currently playing
     * samples each time a new sample is played (0 = mute, 1 = no decrease).
     * The decrease is cumulative, i.e, if volumeDecrease is 0.5 we will
     * have the following behavior:
     * [sampleA vol=1]
     * [sampleA vol=0.5] [sampleB vol=1]
     * [sampleA vol=0.25] [sampleB vol=0.5] [sampleC vol=1]
     * A value of 0 is equivalent to setting "channels" to 1. 
     * 
     * @param volumeFadeOut
     * Same as volumeDecrease, except that the decrease will take
     * some time (specified by fadeOutTime). For example, if
     * volumeFadeOut=0 and fadeOutTime=1, previous playing samples
     * will fade out in one second. If volumeFadeOut is 1, only
     * the instant volumeDecrease will be applied.
     * 
     * For example, for values of volumeDecrease=0.8, volumeFadeOut=0.5
     * and fadeOutTime=1.5, the volume of the last playing sample
     * will jump from 1 to 0.8 as a new sample is played, then
     * will fade out to 0.4 (0.8*0.5) in 1.5 s :
     * 
     * v = 1  ------|
     *              |   v = 0.8
     *               \ 
     *                \
     *                 \
     *                  \------------  v = 0.4
     *
     *             | 1.5 s|
     * 
     * @param fadeOutTime
     * The decay time of volumeFadeOut, in seconds. 
     */
    public void setBlending(int channels, float volumeDecrease, float volumeFadeOut, float fadeOutTime) {
        this.channels = channels;
        stopAll();
        playing = new StereoSample[channels];
    	this.immediateFadeOutRatio = volumeDecrease;
    	this.fadeOutRatio = volumeFadeOut;
    	this.fadeOutTime = fadeOutTime;
    }
    
    public void setBlending(int channels) {
    	this.channels = channels;
        stopAll();
        playing = new StereoSample[channels];
    }
    
    /**
     * Adds a stereo sample.
     * 
     * @param index
     * The sample index. This number will then be used for playing the
     * sample.
     * 
     * @param filename
     * The sample file name, in any PCM (.wav) format.
     * 
     * @param location
     * An integer representing the location of the source:
     * 
     *         7   0   1
     *       6     .     2
     *         5   4   3
     *         
     * To play a sound in the center, you can specify 0. Currently,
     * 0 is same as 4, 7 is the same as 5 and 1 is the same as 3.
     * 
     * @param delay
     * An optional silence in seconds, inserted at the beginning of
     * the sample.
     * 
     * @param noiseLevel
     * The level of noise (between 0 and 1) used for trimming the file.
     */
    public void setSample(int index, String filename, float location, float delay, float noiseLevelBefore, float noiseLevelAfter) {
        setSample(index, filename, interpolate(SoundManager.params3D, location, 0), interpolate(SoundManager.params3D, location, 1), interpolate(SoundManager.params3D, location, 2) + delay, interpolate(SoundManager.params3D, location, 3) + delay, noiseLevelBefore, noiseLevelAfter);
    }

    /**
     * Uses a soundlevel of 0.05f, which is correct for good-quality audio
     */
    public void setSample(int index, String filename, float location, float delay) {
        setSample(index, filename, interpolate(SoundManager.params3D, location, 0), interpolate(SoundManager.params3D, location, 1), interpolate(SoundManager.params3D, location, 2) + delay, interpolate(SoundManager.params3D, location, 3) + delay,
        		0.06f, 0.005f);
    }
    
    public void setSample(int index, String filename, float location, float delay, float volume) {
        setSample(index, filename, interpolate(SoundManager.params3D, location, 0)*volume, interpolate(SoundManager.params3D, location, 1)*volume, interpolate(SoundManager.params3D, location, 2) + delay, interpolate(SoundManager.params3D, location, 3) + delay,
        		0.06f, 0.005f);
    }

    protected float interpolate(float[][] array, float index, int index2) {
    	int i1 = ((int)index)%8;
    	int i2 = ((int)index+1)%8;
    	float t = index-(int)index;
    	float value = array[i1][index2]*(1-t) + array[i2][index2]*t;
    	return value;
    }
    
    /**
     * Plays the sample immediately.
     */
    public StereoSample play(int sample) {
    	if (sample < 0 || sample > samples.length-1)
    	  	return null;
        if (samples[sample] == null) return null;
        
        // clone sample's playing parameters to allow multiple playings of the same sample
        StereoSample pl = new StereoSample(samples[sample]);
        pl.setLength(pl.getSampleLength());
        play(pl);
        return pl;
    }
    
    /**
     * Plays the sample immediately.
     */
    public StereoSample play(int sample, int len) {
    	if (sample < 0 || sample > samples.length-1)
    	  	return null;
        if (samples[sample] == null) return null;
        
        // clone sample's playing parameters to allow multiple playings of the same sample
        StereoSample pl = new StereoSample(samples[sample]);
        pl.setLength(pl.getSampleLength()/len);
        play(pl);
        return pl;
    }
    
    /**
     * Plays the sample immediately if it is not already playing,
     * to avoid repetitions.
     * 
     * PLEASE NOTE:
     * If the sample is already playing but with a very low volume,
     * this method will still refuse to play it again.
     */
    public StereoSample playUnique(int sample) {
    	if (sample < 0 || sample > samples.length-1)
    	  	return null;
        if (samples[sample] == null) return null;
        
        if (!isPlaying(sample)) {
	        // clone sample's playing parameters to allow multiple playings of the same sample
	        StereoSample pl = new StereoSample(samples[sample]);
	        play(pl);
	        return pl;
        }
        
        return null;
    }
    
    /**
     * Plays the sample as soon as no more sound is being played
     * in this soundset.
     * 
     * If nothing is being currently played, the sample will be played
     * immediately.
     * This method can be called several times, in which case
     * the postponed samples will be played in succession.
     * 
     * PLEASE NOTE:
     * If other samples are played immediately after this one,
     * the postponed sample will also have to wait for them to
     * be finished. So this method does not waits for the current
     * sounds to finish. Instead, it waits for the buffer to be empty.
     */
    public StereoSample playWhenEmpty(int sample) {
    	if (sample < 0 || sample > samples.length-1)
    	  	return null;
        if (samples[sample] == null) return null;

        // clone sample's playing parameters to allow multiple playings of the same sample
        StereoSample pl = new StereoSample(samples[sample]);
        
        if (nplaying == 0)
        	play(pl);
        else {
        	pendingPlays.add(new PendingPlay(pl, null));
        }
       
        return pl;
    }
    
    /**
     * Plays sample after sample0.
     * 
     * If sample0 is not playing, sample will be played immediately.
     * In the other case, it will be played as soon as there is no
     * more instance of sample0 playing.
     * 
     * PLEASE NOTE:
     * Currently, samples are not trimmed at the end. So the silence
     * between samples using this method may vary.
     */
    public StereoSample playAfter(int sample, int sample0) {
    	if (sample < 0 || sample > samples.length-1)
    	  	return null;
        if (samples[sample] == null) return null;

        // clone sample's playing parameters to allow multiple playings of the same sample
        StereoSample pl = new StereoSample(samples[sample]);
        
        if (!isPlaying(sample0))
        	play(pl);
        else {
        	pendingPlays.add(new PendingPlay(pl, new Integer(sample0)));
        }
       
        return pl;
    }
    
    /**
     * Stops all sounds currently playing.
     * 
     * If there are pending samples, they will be played afterwards.
     */
    public void stopAll() {
        for (int i=0; i<nplaying; i++) {
            removePlaying(i);
            i--;
        }
        perhapsPlayPending();
    }
    

    /**
     * Returns the duration of the given sample, in seconds.
     */
    public float getDuration(int sample) {
    	if (sample < 0 || sample > samples.length-1)
    	  	return 0;
        if (samples[sample] == null) return 0;
    	return samples[sample].getDuration(); // stereo
    }

    
    ///////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////

    protected void setSample(int index, String fn, float leftVolume, float rightVolume, float leftDelay, float rightDelay, float noiseLevelBefore, float noiseLevelAfter) {
    	  if (index < 0 || index > samples.length-1)
    	  	return;
        samples[index] = new StereoSample(this, index, SoundManager.srate, fn, leftVolume, rightVolume, leftDelay, rightDelay, noiseLevelBefore, noiseLevelAfter);
        if (index > maxindex) maxindex = index;
    }
    
    protected boolean isPlaying(int sample) {
    	if (sample < 0 || sample > samples.length-1)
    	  	return false;
        if (samples[sample] == null) return false;

        for (int i=0; i<nplaying; i++) {
            if (playing[i].prototype == samples[sample])
            	return true;
        }
        return false;
    }
    
        
    protected void play(StereoSample pl) {
        // fade out other samples
        if (fadeOutRatio < 1) {
        	fadeOutPlaying();
        }
        
        // if needed, stop the oldest current sample
        if (nplaying == channels) {
           removePlaying(0);
        }
        playing[nplaying] = pl;
        nplaying++;
        pl.play();
    }
    
    protected void fadeOutPlaying() {
        for (int i=0; i<nplaying; i++) {
        	playing[i].setVolume(playing[i].volume * immediateFadeOutRatio);
            playing[i].animateVolume(playing[i].getVolume() * fadeOutRatio, fadeOutTime);
        }
    }
        
    /*
    public void setVolume(int sample, float vol) {
     	if (sample < 0 || sample > samples.length-1)
    	  	return;
        if (samples[sample] == null) return;
        
        for (int i=0; i<nplaying; i++) {
            if (playing[i].prototype == samples[sample]);
            playing[i].setVolume(vol);
        }
    }*/
   
    /** Compute the next buffer. If not hit return zero.
     */
    public void computeBuffer() {
        int bufsz = getBufferSize();

        for(int k=0; k<bufsz; k++) {
            float y = 0;
            for (int i=0; i<nplaying; i++) {
            	//there is a weired bug here sometimes, I added this statement, so this problem will
            	//not happen. I am not sure why though
            	if(playing[i] != null)
            		y += playing[i].getNextSample();
            }
            buf[k] = y * volume;
        }
        
        for (int i=0; i<nplaying; i++) {
            if (!playing[i].playing() || (playing[i].volume == 0 && playing[i].destvolume == 0)) {
                removePlaying(i);
                i--;
            }
        }
        perhapsPlayPending();
    }
    
    protected void removePlaying(int index) {
    	if (index > nplaying-1)
    		return;
        playing[index].stop();
        playing[index] = null;
        for (int i=index; i<nplaying && i<channels-1; i++) {
            playing[i] = playing[i+1];
        }
        nplaying--;
    }
    
    protected void perhapsPlayPending() {
    	
    	if (pendingPlays.isEmpty()) return;
    	
    	for (Enumeration e = pendingPlays.elements(); e.hasMoreElements();) {
    		PendingPlay pp = (PendingPlay)e.nextElement();
    		if (pp.isOK()) {
    			play(pp.sample);
    			pendingPlays.removeElement(pp);
    		}
    	}
    }
}




